#include "LuaBinder/Header.h"
#include "LuaBinder/Table.h"

namespace XGC
{
	namespace lua
	{
		Table::Table(const std::string &sep, bool c) : _cacheable(c), _separator(sep) {XGC_LUA_DEBUG_CALL;}
		Table::~Table() {XGC_LUA_DEBUG_CALL;}
			
		Object* Table::rawGet(const std::string &name)
		{
			XGC_LUA_DEBUG_CALL;
			Elements::iterator i = _elements.find(name);
			if (i == _elements.end())
			{
				XGC_LUA_DEBUG(10, "Access Table(%p) [%s] (FAILED!)", this, name.c_str());
				return 0;
			}
			XGC_LUA_DEBUG(10, "Access Table(%p) [%s] (OK!)", this, name.c_str());
			return i->second.get();
		}

		inline void Table::rawSet(const std::string &name, Object *obj)
		{
			XGC_LUA_DEBUG_CALL;
			if (obj == 0)
			{
				XGC_LUA_DEBUG(6, "Table (%p) remove '%s'", this, name.c_str());
				_elements.erase(name);
			}
			else
			{
				XGC_LUA_DEBUG(6, "Table (%p) [%s] = %p", this, name.c_str(), obj);
				_elements[name] = obj;		
			}
		}

		Object* Table::get(const std::string &name)
		{
			XGC_LUA_DEBUG_CALL;
			TableFind t = getTable(name, false);
			if (t.first != 0) return t.first->rawGet(t.second);
			return 0;
		}

		void Table::getElements( std::list< const char* > &l )const
		{
			Elements::const_iterator i = _elements.begin();
			while( i != _elements.end() )
			{
				l.push_back( i->first.c_str() );
				++i;
			}
		}

		void Table::erase(const std::string &name)
		{
			XGC_LUA_DEBUG_CALL;
			set(name, 0);
		}

		void Table::setCache(lua_State *L)
		{
			XGC_LUA_DEBUG_CALL;
			XGC_LUA_DEBUG_CLEAN_STACK(L,-2);
			XGC_LUA_DEBUG_STACK(8, L, "Table(%p) :: setCache BEGIN ", this);
			int top = lua_gettop(L);
			if (top < 2 ) luaL_error(L, "Not enough elements to perform Table::setCache");
			push(L); // push ::Table
			if (luaL_getmetafield(L,-1, "__indexCache"))
			{
				lua_insert(L, top - 1); // move the metatable above key,value
				lua_settop(L, top + 1); // remove everything else
				lua_rawset(L,-3);
			}
			else
			{
				luaL_error(L, "Invalid setCache;  %s:%d", __FILE__, __LINE__ );
			}
			XGC_LUA_DEBUG_STACK(8, L, "Table(%p) :: setCache END original top = %d", this, top);
			lua_settop(L, top - 2);
		}

		void Table::getCache(lua_State *L)
		{
			XGC_LUA_DEBUG_CALL;
			XGC_LUA_DEBUG_CLEAN_STACK(L,0);
			XGC_LUA_DEBUG_STACK(8, L, "Table(%p) :: getCache BEGIN ", this);
			int top = lua_gettop(L);
			if (top < 1 ) luaL_error(L, "Not enough elements to perform Table::getCache");
			push(L); // [+1] push ::Table 
			if (!luaL_getmetafield(L,-1, "__indexCache")) // [+1]
			{
				luaL_error(L, "Invalid setCache;  %s:%d", __FILE__, __LINE__ );
			}
			lua_pushvalue(L, top); // [+1] copy value
			lua_rawget(L, -2); // [-2]
			XGC_LUA_DEBUG_STACK(8, L, "Table(%p) :: getCache END (result is at top) top was = %d", this, top);
			lua_replace(L, top); // [-1,+1] as result
			lua_settop(L, top);
		}


		void Table::set(const std::string &name, Object *obj)
		{
			XGC_LUA_DEBUG_CALL;
			TableFind t = getTable(name, true);
			t.first->rawSet(t.second, obj);
		}

		Table::TableFind Table::getTable(const std::string &key, bool create)
		{
			XGC_LUA_DEBUG_CALL;
			if (_separator.empty()) return TableFind(this,key);

			std::string::size_type pos = key.find(_separator);
			if (pos != std::string::npos)
			{
				const std::string &base = key.substr(0, pos);
				const std::string &next = key.substr(pos+_separator.length());

				Table* subtable = dynamic_cast<Table*>(rawGet(base));
				
				if (subtable == 0)
				{
					if (create)
					{
						XGC_LUA_DEBUG(6, "Table (%p) create Subtable %s -> %s", this, 
							base.c_str(), next.c_str());

						subtable = new Table(_separator, _cacheable);
						rawSet(base,subtable);
					}
					else
					{
						return TableFind(xgc_nullptr,key); // not found
					}
				}

				return subtable->getTable(next, create); //< TODO: recursivity... replace with iterative version.
			}
			// else :
			return TableFind(this,key);
		}
		
		int Table::__index(lua_State *L)
		{
			XGC_LUA_DEBUG_CALL;
			XGC_LUA_DEBUG_STACK(10,L,"Table::__index (%p)",this);
			int result = -1;
			
			if (_cacheable)
			{
				lua_pushvalue(L,2);
				lua_rawget(L, cacheTableIndex());
				if (lua_isnil(L,-1)) lua_pop(L,1); // remove nil
				else
				{
					//TODO tostring modifies the object, use a ifdef endif block here
					XGC_LUA_DEBUG(10, "Access Table(%p) [%s] (In CACHE)", this, lua_tostring(L,2));
					result = 1; // we found it
				}
			}

			if (result < 0)
			{
				if (lua_type(L, 2) == LUA_TSTRING)
				{
					const char* key = lua_tostring(L,2);
					Object *obj = get(key);
					if (obj)
					{
						result = 1;
						obj->push(L);
						if (_cacheable)
						{
							// store in the cache...
							XGC_LUA_DEBUG(10, "L(%p) table(%p) key %s (->to cache)", L, this, lua_tostring(L,2));
							lua_pushvalue(L,2); // key
							lua_pushvalue(L,-2); // returned value
							lua_rawset(L, cacheTableIndex() ); // table
						}
					}
				}
			}

			return result;
		}
		
		int Table::__newindex(lua_State *L)
		{
			int result = 0;
			XGC_LUA_DEBUG_CALL;
			XGC_LUA_DEBUG_STACK(10,L,"Table::__newindex (%p)",this);
			luaL_error(L, "(%p)__newindex metamethod not implemented", (void*)this);
			return result;
		}

		int Table::__call(lua_State *L)
		{
			XGC_LUA_DEBUG_CALL;
			XGC_LUA_DEBUG_STACK(10,L,"Table::__call (%p)",this);
			luaL_error(L, "(%p)__call metamethod not implemented", (void*)this);
			return 0;
		}

		int Table::__garbageCollector(lua_State *L)
		{
			XGC_LUA_DEBUG_CALL;
			XGC_LUA_DEBUG_STACK(10,L,"Table::__GC (%p)",this);
			luaL_error(L, "(%p) __gc metamethod not implemented", (void*)this);
			return 0;
		}

		int Table::__tostring(lua_State *L)
		{
			XGC_LUA_DEBUG_CALL;
			XGC_LUA_DEBUG_STACK(10,L,"Table::__tostring (%p)",this);
			int top = lua_gettop(L);
			lua_pushfstring(L, "Table(%p) [%s] with keys:", this, typeid(*this).name());
			for(Elements::iterator i = _elements.begin(); i != _elements.end(); ++i)
			{
				lua_pushfstring(L, "\n\t%s -> %p [%s]",i->first.c_str(), i->second.get(), typeid(*(i->second.get())).name());
			}
			lua_concat(L, lua_gettop(L) - top);
			return 1;
		}

		void Table::pushImplementation(lua_State *L)
		{
			XGC_LUA_DEBUG_CALL;
			lua_newtable(L); // an NEW empty table

			lua_newtable(L); // and its metatable:

			lua_newtable(L); // cache

			lua_pushvalue(L, -1); 
			lua_setfield(L, -3, "__indexCache");

			pushMeta(L, &Table::__indexProxy);
			lua_setfield(L,-3, "__index");
			pushMeta(L, &Table::__newindex);
			lua_setfield(L, -3, "__newindex");
			pushMeta(L, &Table::__tostring);
			lua_setfield(L, -3, "__tostring");
			pushMeta(L, &Table::__call);
			lua_setfield(L, -3, "__call");
			pushMeta(L, &Table::__garbageCollector);
			lua_setfield(L, -3, "__gc");

			lua_pop(L,1); // remove the cache table

			lua_setmetatable(L,-2);
		}

		int Table::__indexProxy(lua_State *L)
		{
			XGC_LUA_DEBUG_CALL;
			XGC_LUA_DEBUG(9, "---> __index search");
			XGC_LUA_DEBUG_STACK(10,L,"Table::__indexProxy (%p)",this);
			int result = __index(L);
			
			if (result < 0)
			{
				XGC_LUA_DEBUG(9, "Nothing found....");
				/* TODO Change the result behaviour, resturn 0 when nothing is found
				 * and quit using result = -1 */
				result = 0;
			}
			XGC_LUA_DEBUG(9, "<--- __index result = %d", result);
			return result;
		}

		
		int Table::__meta(lua_State *L)
		{
			XGC_LUA_DEBUG_CALL;
			// upvalue(1) is the cache table...
			Table *table = reinterpret_cast<Table*>(lua_touserdata(L, lua_upvalueindex(2)));
			TableMember member = *reinterpret_cast<TableMember*>(lua_touserdata(L, lua_upvalueindex(3)));
			return (table->*member)(L);
		}
		
		/** Pushmeta exepects a cache-table at the top, and creates a metamethod with 3 upvalues,
		 * first the cache table, second the current object, and last the member to call */
		void Table::pushMeta(lua_State *L, Table::TableMember member) const
		{
			assert("Invalid pushMeta, expected a table at the top (the cache-table)" &&
				lua_type(L,-1) == LUA_TTABLE);
			lua_pushvalue(L,-1); // copy of cache table
			lua_pushlightuserdata(L, (void*) this);
			void *p = lua_newuserdata(L, sizeof(TableMember)); 
			memcpy(p,&member,sizeof(TableMember));

			lua_pushcclosure(L, __meta, 3 );
		}
	}	
}
